' CYCLES2D
' written by the Math Wizard
' July 2010
' Tron Lightcycle game clone in 2D.
' Goals: 1) Modularize display to allow 2.5D view.
'        2) Modularize controls to allow different paradigms:
'           cycle-centric & board-centric
'           a) Board-centric: use up/down/left/right to 
'              control own cycle (Arcade version)
'           b) Cycle-centric: use left/right to turn 
'              own cycle (Armagetron, GLTron)
'        3) Hotseat multiplayer
'        4) Network multiplayer?

' direction/bearing constants
Const E = 0
Const N = 1
Const W = 2
Const S = 3

' player/cpu constants
Const COMP = 0
Const ERRATIC = 2	' how erratic will computer cycle be? (1-99)

' other constants
Const NUMCYCLES = 2
Const GRIDHEIGHT = 150
Const GRIDWIDTH = 150

Type Cycle
    As Integer who          ' 0 = Computer; > 0 = Human
    As Integer face         ' direction cycle currently facing
    As Integer hue          ' cycle/trail color
    As Integer alive
    As Integer x, y

    Declare Sub Move()
    Declare Sub Turn(ByVal f As Integer)
    Declare Sub TurnL()
    Declare Sub TurnR()
End Type

Sub Cycle.Move()
' If the current cycle is still alive,
' move it in the direction it is facing.
' TODO: Implement "speed". Currently all bikes travel
'       at the same speed (1 position/loop)
'       Turns out 1 position/loop is blazing fast
'       without the delay.

If this.alive <> 0 Then
    Select Case face
        Case E: this.x += 1
        Case N: this.y += 1
        Case W: this.x -= 1
        Case S: this.y -= 1
    End Select
End If

End Sub 

Sub Cycle.Turn(ByVal f As Integer)
' Changes the direction of the cycle.
' f should be one of the direction constants.
' Does nothing if the player requests a 180 degree turn
' (other engines kill you at that point, which is annoying)

' Since E and W are both even, and N and S are both odd,
' just check the parity of (f + .face). This code will also
' do nothing if the player requests the same direction as
' he or she is already travelling.
If ((f + this.face) Mod 2) > 0 Then
    this.face = f
End If

End Sub

Sub Cycle.TurnL()
' turns the Cycle left relative to
' the current direction.

If this.face = S Then
    this.face = E
Else
    this.face = this.face + 1
End If

End Sub

Sub Cycle.TurnR()
' turns the Cycle right relative to
' the current direction.

If this.face = E Then
    this.face = S
Else
    this.face = this.face - 1
End If

End Sub
    

' Game functions
' defined here so no Declares are needed

Sub Show(g() As Integer, c() As Cycle)
' shows the state of the game.
' Draws the grid with the cycle's position highlighted.

Dim As Integer x, y, i

' Draw the box
Line (0, 0)-(GRIDWIDTH + 1, GRIDHEIGHT + 1), 15, B

' Draw the cycle walls
For x = 1 To GRIDWIDTH
    For y = 1 To GRIDHEIGHT
        If g(x, y) > 0 Then PSet (x, (GRIDHEIGHT - y)), g(x, y)
    Next y
Next x

' Draw the cycles
For i = 1 To NUMCYCLES
    PSet (c(i).x, (GRIDHEIGHT - c(i).y)), 15
Next i

End Sub

Function Look(g() As Integer, _
  ByVal x As Integer, ByVal y As Integer) As Integer
' Is there a wall (cycle or game grid) in the position specified?

' check game grid boundaries
If x < 1 Or y < 1 Or x > GRIDWIDTH Or y > GRIDHEIGHT Then
    Return 1
' check cycle walls
ElseIf g(x, y) > 0 Then
    Return 1
Else
    Return 0
End If

End Function

Sub CompControl(g() As Integer, ByRef c As Cycle)
' handles computer control of cycle.
' Move randomly while trying to avoid walls (for now).

Dim As Integer heck	    ' gives randomness to turn decisions
Dim As Integer ahead	    ' how many grids to look ahead
Dim As Integer doit, lr	    ' turn or not? left or right?

' lookahead will vary
ahead = Int(Rnd * 3) + 8
heck = Int(Rnd * 100) + 1

Select Case c.face
    Case E: If Look(g(), c.x + ahead, c.y) Or heck < ERRATIC Then doit = 1
    Case N: If Look(g(), c.x, c.y + ahead) Or heck < ERRATIC Then doit = 1
    Case W: If Look(g(), c.x - ahead, c.y) Or heck < ERRATIC Then doit = 1
    Case S: If Look(g(), c.x, c.y - ahead) Or heck < ERRATIC Then doit = 1
End Select

If doit = 1 Then
    lr = Int(Rnd * 2) + 1
    If lr = 1 Then
	c.TurnL
    Else
	c.TurnR
    End If
End If

End Sub

Sub UserControl(ByRef c As Cycle)
' handles user control of cycle.
' Process keyboard presses by the User.
' This version assumes only 1 User.
' TODO: key map is not yet configurable

Dim As String k

k = Inkey
k = UCase(k)
Select Case k
    Case "W": c.Turn(N)
    Case "A": c.Turn(W)
    Case "S": c.Turn(S)
    Case "D": c.Turn(E)
End Select

End Sub


' Main Program

' Game Objects
Dim As Integer grid(1 To GRIDWIDTH, 1 To GRIDHEIGHT)
Dim As Cycle cyc(1 To NUMCYCLES)

' Set up the test cycles
cyc(1).who = 1
cyc(1).face = E
cyc(1).hue = 1
cyc(1).alive = 1
cyc(1).x = 10
cyc(1).y = 75

cyc(2).who = COMP
cyc(2).face = W
cyc(2).hue = 3
cyc(2).alive = 1
cyc(2).x = 140
cyc(2).y = 75

' Misc setup
Randomize Timer
Dim As Integer i

' Set up the display system
Screen 14
Cls
Print "Cycles2D"
Print "by the Math Wizard"
Print "Controls: WASD, X to exit."
Print "Press any key to begin."
Sleep
Cls

' Start running the game
' This version will raise cycle walls but
' won't check if you collided against them.
' It will kill you if you exit the game grid.
Do
    Show grid(), cyc()
    For i = 1 To NUMCYCLES
	' control the cycles
	If cyc(i).who = COMP Then
	    CompControl(grid(), cyc(i))
	Else
	    UserControl(cyc(i))
	End If
	
	' check for collisions
	If Look(grid(), cyc(i).x, cyc(i).y) > 0 Then 
	    cyc(i).alive = 0
	Else
	    grid(cyc(i).x, cyc(i).y) = cyc(i).hue
	    cyc(i).Move
	End If
	Sleep 10
    Next i

Loop While cyc(1).alive <> 0 And cyc(2).alive <> 0

Cls
If cyc(1).alive And Not cyc(2).alive Then 
    Print "User wins."
ElseIf cyc(2).alive And Not cyc(1).alive Then
    Print "Computer wins."
Else
    Print "ERROR!"
End If
Print "End of line."

Sleep

End
